unit tilepaletteunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, ComCtrls, ExtCtrls, fpjson,
  tilepalettedrawerunit, Graphics, StdCtrls, tilesetgraphicsunit,
  BGRABitmapTypes, BGRABitmap, tileselectionunit, palettehelper,
  LCLTranslator;

type

  { TTilePaletteFrame }
  TTilePaletteType = (tptDrawing, tptEditing);
  TEditedTileSetting = (etsPassability, etsFourDir, etsSingleBit, etsTerrainTag);


  TTilePaletteFrame = class(TFrame)
    IdleTimerDrawPalette: TIdleTimer;
    UpdatingLabel: TLabel;
    TilePalettePaintBox: TPaintBox;
    TilePaletteScrollBox: TScrollBox;
    TilePaletteTabControl: TTabControl;
    procedure IdleTimerDrawPaletteTimer(Sender: TObject);
    procedure TilePalettePaintBoxMouseDown(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure TilePalettePaintBoxMouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
    procedure TilePalettePaintBoxMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure TilePalettePaintBoxPaint(Sender: TObject);
    procedure TilePaletteScrollBoxResize(Sender: TObject);
    procedure TilePaletteTabControlChange(Sender: TObject);
  private
    Drawer: TTilePaletteDrawer;
    PaletteBitmap: TBGRABitmap;
    ResizedBitmap: TBGRACustomBitmap;
    TilesetGraphics: TTilesetGraphics;
    PaletteBeingDrawn: Boolean;
    NeedsResize: Boolean;
    FType: TTilePaletteType;

    {Only used when FType = tptEditing}
    TilesetData: TJSONArray;
    EditedTileSetting: TEditedTileSetting;
    EditedBit: Integer;

    {Only used when FType = tptDrawing}
    SelTab: Integer;
    SelRect: TRect;
    SelStartX, SelStartY: Integer;
    SelStarted: Boolean;


    IsSingleLayer: Boolean;
    IsWorldType: Boolean;
    procedure UpdateSelection(X, Y: Integer);
    function IsSelectionValid: Boolean;
    function MakeSelection: TTileSelection;
    function GetFlags(TileId: Integer): Dword;
  public
    UpdateCallback: SelectionUpdateCallback;
    constructor Create(TheOwner: TComponent); override;
    procedure SetType(AType: TTilePaletteType);
    procedure UpdatePaletteImage;
    procedure UpdatePaletteSize;
    procedure DoPaletteUpdate;
    procedure UpdateFromTileset(ATilesetGraphics: TTilesetGraphics; Tileset: TJSONObject);
    procedure SelectMapLayer(LayerId: Integer);
    procedure SetEditedData(ATilesetData: TJSONArray; AEditedTileSetting: TEditedTileSetting; AEditedBit: Integer = 0);

    //procedure SetEditedSetting();

    destructor Destroy; override;
  end;

implementation

uses
  globals, math, Dialogs, constants, Types, autotilehelper;

{$R *.lfm}

constructor TTilePaletteFrame.Create(TheOwner: TComponent);
begin
  inherited;
  Drawer := TTilePaletteDrawer.Create;
end;

procedure TTilePaletteFrame.SetType(AType: TTilePaletteType);
begin
  FType := AType;
end;

procedure TTilePaletteFrame.UpdateFromTileset(ATilesetGraphics: TTilesetGraphics; Tileset: TJSONObject);
var
  Mode: Integer;
  ModeJson: TJSONNumber;
begin
  Mode := 0;
  if Tileset.Find('mode', ModeJson) then
    Mode := ModeJson.AsInteger;

  TilesetGraphics := ATilesetGraphics;
  SelTab := -1;
  IsWorldType := Mode = 0;
  UpdatePaletteImage;
  TilePalettePaintBox.Visible := False;
end;

procedure TTilePaletteFrame.UpdatePaletteImage;
begin
  if PaletteBitmap <> nil then begin
    PaletteBitmap.Free;
    PaletteBitmap := nil;
  end;
  if TilesetGraphics <> nil then begin
    PaletteBeingDrawn := True;
    UpdatingLabel.Visible := True;
    TilePalettePaintBox.Visible := False;
    IdleTimerDrawPalette.Enabled := True;
  end
  else begin
    if ResizedBitmap <> nil then begin
      ResizedBitmap.Free;
      ResizedBitmap := nil;
    end;
    TilePalettePaintBox.Visible := False;
  end;
  Refresh;
end;

procedure TTilePaletteFrame.UpdatePaletteSize;
var
  NewHeight: Integer;
begin
  if PaletteBitmap = nil then begin
    TilePalettePaintBox.Visible := False;
    Exit;
  end;

  TilePalettePaintBox.Visible := True;
  NewHeight := PaletteBitmap.Height * (TilePaletteScrollBox.ClientWidth - 20)
                                  div PaletteBitmap.Width;
  //TODO: remove this hack with 20, add some normal way to get scrollbar width
  //or some way to resize that without going into an endless loop

  TilePalettePaintBox.ClientHeight := NewHeight;
  TilePaletteScrollBox.ClientWidth := TilePaletteScrollBox.ClientWidth - 20;

  if ResizedBitmap <> nil then
    ResizedBitmap.Free;
  ResizedBitmap := PaletteBitmap.Resample(
                TilePalettePaintBox.ClientWidth,
                TilePalettePaintBox.ClientHeight,
                rmSimpleStretch
  );
end;

function TTilePaletteFrame.GetFlags(TileId: Integer): Dword;
begin
  if (TileId >= 0) and (TileId < TilesetData.Count) and (TilesetData[TileId].JSONType = jtNumber) then
    GetFlags := DWord(TilesetData.Integers[TileId])
  else
    GetFlags := GetDefaultTileFlags(TileId);
end;

procedure TTilePaletteFrame.TilePalettePaintBoxPaint(Sender: TObject);
var
  OriginalTileWidth, OriginalTileHeight: Integer;
  ResizedTileWidth, ResizedTileHeight: Double;

  procedure DrawSelection;
  begin
    TilePalettePaintBox.Canvas.Pen.Width := 3;
    TilePalettePaintBox.Canvas.Pen.Color := clWhite;
    TilePalettePaintBox.Canvas.Brush.Style := bsClear;
    TilePalettePaintBox.Canvas.Rectangle(
      Math.floor(SelRect.Left * ResizedTileWidth) + 1,
      Math.floor(SelRect.Top * ResizedTileHeight) + 1,
      Math.ceil(SelRect.Right * ResizedTileWidth) -2,
      Math.ceil(SelRect.Bottom * ResizedTileHeight) -2
    );
    TilePalettePaintBox.Canvas.Pen.Width := 1;
    TilePalettePaintBox.Canvas.Pen.Color := clBlack;
    TilePalettePaintBox.Canvas.Rectangle(
      Math.floor(SelRect.Left * ResizedTileWidth) + 1,
      Math.floor(SelRect.Top * ResizedTileHeight) + 1,
      Math.ceil(SelRect.Right * ResizedTileWidth) - 2,
      Math.ceil(SelRect.Bottom * ResizedTileHeight) - 2
    );
  end;

  procedure DrawFlags;
  var
    X, Y: Integer;

    procedure TextOnTile(Text: String);
    var
      Extent: TSize;
      TextX, TextY: Integer;
    begin
      with TilePalettePaintBox.Canvas do begin
          Extent := TextExtent(Text);
          TextX := Ceil(X * ResizedTileWidth) + (Ceil(ResizedTileWidth) - Extent.cx) div 2;
          TextY := Ceil(Y * ResizedTileHeight) + + (Ceil(ResizedTileHeight) - Extent.cy) div 2;


          Font.Color := clBlack;
          TextOut(TextX -1, TextY -1, Text);
          TextOut(TextX -1, TextY + 1, Text);
          TextOut(TextX +1, TextY -1, Text);
          TextOut(TextX +1, TextY +1, Text);

          TextOut(TextX -1, TextY, Text);
          TextOut(TextX -1, TextY, Text);
          TextOut(TextX, TextY -1, Text);
          TextOut(TextX, TextY +1, Text);

          Font.Color := clWhite;
          TextOut(TextX, TextY, Text);

      end;
    end;

    function GetBit(TileId, BitNumber: Integer): Boolean;
    begin
      GetBit := ((GetFlags(TileId) shr BitNumber) and 1) > 0
    end;

    procedure DrawSingleBitFlag;
    var
      Bit: Boolean;
      Item: String;
      Tile: TPaletteTile;
    begin
      Tile := GetPaletteTile(X, Y, TilePaletteTabControl.TabIndex, TilesetGraphics);
      Bit := GetBit(Tile.MainTileId, EditedBit);

      if Bit then begin
        case EditedBit of
          5: Item := 'H';
          6: Item := '≈';
          7: Item := '◆';
          8: Item := 'W';
          9..11: Item := '#';
        else
          Item := '+';
        end;
      end
      else
        Item := '.';

      TextOnTile(Item);
    end;

    procedure DrawTriangle(X1, Y1, X2, Y2, X3, Y3: Integer); inline;
    var
      Points: array [1..3] of TPoint;
    begin
      with TilePalettePaintBox.Canvas do begin
          Brush.Style := bsSolid;
          Pen.Style := psSolid;
          Brush.Color := clWhite;
          Pen.Color := clBlack;

          Points[1] := Point(X1, Y1);
          Points[2] := Point(X2, Y2);
          Points[3] := Point(X3, Y3);


          Polygon(@Points[1], 3, True);
      end;
    end;

    procedure DrawDot(DotX, DotY: Integer); inline;
    begin
      with TilePalettePaintBox.Canvas do begin
          Brush.Style := bsSolid;
          Pen.Style := psSolid;
          Brush.Color := clWhite;
          Pen.Color := clBlack;

          Ellipse(DotX -2, DotY -2, DotX +2, DotY + 2);
      end;
    end;

    procedure DrawPassability;
    var
      Star, FullBlock: Boolean;
      Tile: TPaletteTile;
    begin
      Tile := GetPaletteTile(X, Y, TilePaletteTabControl.TabIndex, TilesetGraphics);

      Star := GetBit(Tile.MainTileId, 4);
      FullBlock := IsFullBlock(GetFlags(Tile.MainTileId));

      if Star then
        TextOnTile('★')
      else if FullBlock then
        TextOnTile('x')
      else
        TextOnTile('o');
    end;

    procedure DrawTerrainTag;
    var
      Tag: Dword;
      Tile: TPaletteTile;
    begin
      Tile := GetPaletteTile(X, Y, TilePaletteTabControl.TabIndex, TilesetGraphics);
      Tag := ((GetFlags(Tile.MainTileId) shr 12) and 7);

      TextOnTile(IntToStr(Tag));
    end;

    procedure DrawFourDir;
    var
      Tile: TPaletteTile;
      TopBlock, LeftBlock, RightBlock, BottomBlock: Boolean;
    begin
      Tile := GetPaletteTile(X, Y, TilePaletteTabControl.TabIndex, TilesetGraphics);

      if IsAutotile(Tile.MainTileId) then
        Exit;
      BottomBlock := GetBit(Tile.MainTileId, 0);
      LeftBlock := GetBit(Tile.MainTileId, 1);
      RightBlock := GetBit(Tile.MainTileId, 2);
      TopBlock := GetBit(Tile.MainTileId, 3);
      if TopBlock then
        DrawDot(
                Round((0.5 + X) * ResizedTileWidth),
                Round((0.25 + Y) * ResizedTileHeight)
        )
      else
        DrawTriangle(
          Round((0.5 + X) * ResizedTileWidth),
          Ceil(Y * ResizedTileHeight),

          Floor((0.4 + X) * ResizedTileWidth),
          Ceil((0.4 + Y) * ResizedTileHeight),

          Ceil((0.6 + X) * ResizedTileWidth),
          Ceil((0.4 + Y) * ResizedTileHeight)
        );

      if LeftBlock then
        DrawDot(
                Round((0.25 + X) * ResizedTileWidth),
                Round((0.5 + Y) * ResizedTileHeight)
        )
      else
        DrawTriangle(
          Floor(X * ResizedTileWidth),
          Round((0.5 + Y) * ResizedTileHeight),

          Ceil((0.4 + X) * ResizedTileWidth),
          Ceil((0.6 + Y) * ResizedTileHeight),

          Ceil((0.4 + X) * ResizedTileWidth),
          Floor((0.4 + Y) * ResizedTileHeight)
        );

      if RightBlock then
        DrawDot(
                Round((0.75 + X) * ResizedTileWidth),
                Round((0.5 + Y) * ResizedTileHeight)
        )
      else
        DrawTriangle(
          Ceil((1 + X) * ResizedTileWidth),
          Round((0.5 + Y) * ResizedTileHeight),

          Floor((0.6 + X) * ResizedTileWidth),
          Ceil((0.6 + Y) * ResizedTileHeight),

          Floor((0.6 + X) * ResizedTileWidth),
          Floor((0.4 + Y) * ResizedTileHeight)
        );

      if BottomBlock then
        DrawDot(
                Round((0.5 + X) * ResizedTileWidth),
                Round((0.75 + Y) * ResizedTileHeight)
        )
      else
        DrawTriangle(
          Round((0.5 + X) * ResizedTileWidth),
          Ceil((Y + 1) * ResizedTileHeight),

          Floor((0.4 + X) * ResizedTileWidth),
          Ceil((0.6 + Y) * ResizedTileHeight),

          Ceil((0.6 + X) * ResizedTileWidth),
          Ceil((0.6 + Y) * ResizedTileHeight)
        );
    end;

    procedure DrawFlagsForCell;
    begin
      case EditedTileSetting of
        etsPassability: DrawPassability;
        etsFourDir: DrawFourDir;
        etsSingleBit: DrawSingleBitFlag;
        etsTerrainTag: DrawTerrainTag;
      end;
    end;

  var
    XEnd, YEnd: Integer;
  begin
    TilePalettePaintBox.Canvas.Brush.Style := bsClear;

    {TODO: edit it to include only visible parts of the scrollbox}
    XEnd := (PaletteBitmap.Width div OriginalTileWidth) - 1;
    YEnd := (PaletteBitmap.Height div OriginalTileHeight) -1;

    for X := 0 to XEnd do
      for Y := 0 to YEnd do
        DrawFlagsForCell;
  end;

begin
  if (ResizedBitmap <> nil) and (PaletteBitmap <> nil) then begin
    ResizedBitmap.Draw(TilePalettePaintBox.Canvas, 0, 0, True);

    OriginalTileWidth := 48; //TODO: adapt for tilewidth <> 48
    OriginalTileHeight := 48; //TODO: adapt for tileheight <> 48

    ResizedTileWidth := OriginalTileWidth * ResizedBitmap.Width / PaletteBitmap.Width;
    ResizedTileHeight := OriginalTileHeight * ResizedBitmap.Height / PaletteBitmap.Height;

    if (SelTab = TilePaletteTabControl.TabIndex) and IsSelectionValid then
      DrawSelection;

    if FType = tptEditing then
      DrawFlags;
  end;
end;

procedure TTilePaletteFrame.DoPaletteUpdate;
var
  TileType: Integer;
begin
  if TilesetGraphics <> nil then begin
    TileType := TilePaletteTabControl.TabIndex;
    PaletteBitmap := Drawer.MakePaletteBitmap(TileType, TilesetGraphics, True);
    UpdatingLabel.Visible := False;
    TilePalettePaintBox.Visible := True;
    PaletteBeingDrawn := False;
    IdleTimerDrawPalette.Enabled := False;
    UpdatePaletteSize;
  end;
end;

procedure TTilePaletteFrame.IdleTimerDrawPaletteTimer(Sender: TObject);
begin
  if PaletteBeingDrawn then
    DoPaletteUpdate;

  if NeedsResize then begin
    UpdatePaletteSize;
    NeedsResize := False;
  end;
end;

procedure TTilePaletteFrame.TilePalettePaintBoxMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if FType = tptDrawing then begin
    SelStartX := X;
    SelStartY := Y;
    SelStarted := True;
  end;
end;

procedure TTilePaletteFrame.TilePalettePaintBoxMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  if SelStarted then begin
    UpdateSelection(X, Y);
    Refresh
  end;
end;

procedure TTilePaletteFrame.TilePalettePaintBoxMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

  procedure EditSelectedTile;
  var
    Tile: TPaletteTile;
    ResizedTileWidth, ResizedTileHeight: Double;
    TileX, TileY: Integer;
    Mask: Dword;

    procedure ToggleBit(BitNumber: Integer);
    var
      Bit, NewBit: Boolean;
      I: Integer;
    begin
      Bit := ((DWord(TilesetData.Integers[Tile.MainTileId]) shr BitNumber) and 1) > 0;
      NewBit := not Bit;

      Mask := 1 shl BitNumber;

      { TODO: handle nulls here }
      for I := Tile.StartTileId to Tile.EndTileId do
        if NewBit then
          TilesetData.Integers[I] := DWord(TilesetData.Integers[I]) or Mask
        else
          TilesetData.Integers[I] := DWord(TilesetData.Integers[I]) and not Mask
    end;

    procedure ToggleDirection;
    var
      PosX, PosY: Double;
      UnderLRDiagonal, UnderRLDiagonal: Boolean;

      {
         ^   /        ^\
         |  /         | \
         | /          |  \
         |/           |   \
         +------->    +----->
         LR diagonal  RL diagonal

        \ over RL /
         \over LR/
          \  Top/
           \   /
     underRL\ / under LR
        Left X  Right
     over LR/ \ over RL
           /   \
          /     \
         / Bottom\
        / underLR \
       /  under RL \
         }
    begin
      if IsAutotile(Tile.MainTileId) then
        Exit;

      PosX := X / ResizedTileWidth - TileX;
      PosY := Y / ResizedTileHeight - TileY;

      UnderLRDiagonal := PosX < PosY;
      UnderRLDiagonal := (1.0 - PosX) < PosY;

      if UnderLRDiagonal and UnderRLDiagonal then
        ToggleBit(0) //bottom
      else if not UnderLRDiagonal and UnderRLDiagonal then
        ToggleBit(2) //right
      else if UnderLRDiagonal and not UnderRLDiagonal then
        ToggleBit(1) //left
      else
        ToggleBit(3) //top
    end;

    procedure ChangeTerrainTag;
    var
      Tag: Integer;
      RemoveMask, AddMask: Dword;
      I: Integer;
    begin
      RemoveMask := not (7 shl 12);

      { TODO: handle nulls here }
      Tag := ((DWord(TilesetData.Integers[Tile.MainTileId]) shr 12) and 7);
      if Button = mbRight then
        Dec(Tag)
      else if Button = mbLeft then
        Inc(Tag);

      if Tag < 0 then
         Tag := Tag + 7;
      if Tag > 7 then
         Tag := Tag - 7;

      AddMask := Tag shl 12;

      for I := Tile.StartTileId to Tile.EndTileId do
        TilesetData.Integers[I] := (DWord(TilesetData.Integers[I]) and RemoveMask)
                                   or AddMask;
    end;

    procedure TogglePassability;
    var
      Star, FullBlock: Boolean;
      TypeIndex: Integer; //1 is o, 2 is x, 3 is *
      RemoveMask, AddMask: DWord;
      I: Integer;
    begin
      Star := ((DWord(TilesetData.Integers[Tile.MainTileId]) shr 4) and 1) > 0;
      FullBlock := IsFullBlock(DWord(TilesetData.Integers[Tile.MainTileId]));

      if IsAutotile(Tile.MainTileId) then begin
        if FullBlock then
          AddMask := 0
        else
          AddMask := 15;
      end else begin
        if Star then
           TypeIndex := 3
        else if FullBlock then
           TypeIndex := 2
        else
          TypeIndex := 1;


        if Button = mbLeft then
          Inc(TypeIndex)
        else if Button = mbRight then
          Dec(TypeIndex);

        if TypeIndex = 0 then
          TypeIndex := 3;
        if TypeIndex = 4 then
          TypeIndex := 1;

        if TypeIndex = 1 then begin
          AddMask := 0;
        end else if TypeIndex = 2 then begin
          AddMask := 15;
        end else begin
          AddMask := 16;
        end;
      end;
      //left: ox*

      RemoveMask := not 31;

      { TODO: handle nulls here }
      for I := Tile.StartTileId to Tile.EndTileId do
        TilesetData.Integers[I] := (DWord(TilesetData.Integers[I]) and RemoveMask)
                                   or AddMask;
    end;

  begin
    ResizedTileWidth := 48 * ResizedBitmap.Width / PaletteBitmap.Width; //TODO: adapt for tilewidth <> 48
    ResizedTileHeight := 48 * ResizedBitmap.Height / PaletteBitmap.Height; //TODO: adapt for tilewidth <> 48

    TileX := Floor(X / ResizedTileWidth);
    TileY := Floor(Y / ResizedTileHeight);

    Tile := GetPaletteTile(TileX, TileY, TilePaletteTabControl.TabIndex, TilesetGraphics);

    while Tile.EndTileId >= TilesetData.Count do
      TilesetData.Add(0); { TODO: verify this is the correct default value }

    case EditedTileSetting of
      etsPassability: TogglePassability;
      etsFourDir: ToggleDirection;
      etsSingleBit: ToggleBit(EditedBit);
      etsTerrainTag: ChangeTerrainTag;
    end;


  end;

begin
  if FType = tptDrawing then begin
    UpdateSelection(X, Y);
    SelStarted := False;
    if UpdateCallback <> nil then
      UpdateCallback(MakeSelection);
  end else if FType = tptEditing then
    EditSelectedTile;

  Refresh;
end;

procedure TTilePaletteFrame.UpdateSelection(X, Y: Integer);
var
  ResizedTileWidth, ResizedTileHeight: Double;
begin
  if (ResizedBitmap = nil) or (PaletteBitmap = nil) then
    Exit;

  ResizedTileWidth := 48 * ResizedBitmap.Width / PaletteBitmap.Width; //TODO: adapt for tilewidth <> 48
  ResizedTileHeight := 48 * ResizedBitmap.Height / PaletteBitmap.Height; //TODO: adapt for tilewidth <> 48

  SelTab := TilePaletteTabControl.TabIndex;

  if SelStartX < X then begin
    SelRect.Left := Floor(SelStartX / ResizedTileWidth);
    SelRect.Right := Min(
                  Ceil(X / ResizedTileWidth),
                  PaletteBitmap.Width div 48);
  end
  else begin
    SelRect.Left := Max(0, Floor(X / ResizedTileWidth));
    SelRect.Right := Ceil(SelStartX / ResizedTileWidth);
  end;

  if SelStartY < Y then begin
    SelRect.Top := Floor(SelStartY / ResizedTileHeight);
    SelRect.Bottom := Min(
                   Ceil(Y / ResizedTileHeight),
                   PaletteBitmap.Height div 48);
  end
  else begin
    SelRect.Top := Max(0, Floor(Y / ResizedTileHeight));
    SelRect.Bottom := Ceil(SelStartY / ResizedTileHeight);
  end;

  if SelRect.Right = SelRect.Left then
    Inc(SelRect.Right);
  if SelRect.Bottom = SelRect.Top then
    Inc(SelRect.Bottom);
end;

function TTilePaletteFrame.MakeSelection: TTileSelection;
var
  Sel: TTileSelection;
  X, Y: Integer;
  ATileSelectFn: ATileSelectionCallback;
  ATileStartY: Integer;
begin
  if SelTab < 0 then begin
    MakeSelection := nil;
    Exit;
  end;

  Sel := TTileSelection.Create;
  Sel.Init(
    SelRect.Right - SelRect.Left,
    SelRect.Bottom - SelRect.Top,
    IsSingleLayer
  );

  for Y := SelRect.Top to SelRect.Bottom do begin
    for X := SelRect.Left to SelRect.Right do begin
      if SelTab = 0 then begin
        ATileSelectFn := Sel.GetATileSelectFn(Y, TilesetGraphics, ATileStartY);
        ATileSelectFn(X - SelRect.Left, Y - SelRect.Top, ATileStartY, Y * 8 + X, IsWorldType)
      end
      else if SelTab > 0 then
        Sel.SelectUpperTile(X-SelRect.Left, Y-SelRect.Top, SelTab, Y * 8 + X);
    end;
  end;

  MakeSelection := Sel;
end;

function TTilePaletteFrame.IsSelectionValid: Boolean;
begin
  IsSelectionValid := (SelTab > -1) and (SelRect.Left <> SelRect.Right)
    and (SelRect.Top <> SelRect.Bottom);
end;

procedure TTilePaletteFrame.TilePaletteScrollBoxResize(Sender: TObject);
begin
  NeedsResize := True;
  IdleTimerDrawPalette.Enabled := True;
end;

procedure TTilePaletteFrame.TilePaletteTabControlChange(Sender: TObject);
begin
  UpdatePaletteImage
end;

procedure TTilePaletteFrame.SelectMapLayer(LayerId: Integer);
var
  NeedsSelectionReset: Boolean;
begin
  NeedsSelectionReset := (LayerId <> LAYER_ALL) <> IsSingleLayer;
  IsSingleLayer := (LayerId <> LAYER_ALL);

  if NeedsSelectionReset then begin
    SelTab := -1;
    SelRect := Rect(0, 0, 0, 0);
    if UpdateCallback <> nil then
      UpdateCallback(MakeSelection);
    Refresh;
  end;
end;

procedure TTilePaletteFrame.SetEditedData(ATilesetData: TJSONArray;
  AEditedTileSetting: TEditedTileSetting; AEditedBit: Integer);
begin
  TilesetData := ATilesetData;
  EditedTileSetting := AEditedTileSetting;
  EditedBit := AEditedBit;

  TilePalettePaintBox.Refresh;
end;

destructor TTilePaletteFrame.Destroy;
begin
  if Drawer <> nil then
    Drawer.Free;
  if PaletteBitmap <> nil then
    PaletteBitmap.Free;

  inherited;
end;

end.

